<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>SVG</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
          integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
    <script src="scripts/snap.svg.js"></script>
    <script src="scripts/d3.v5.js"></script>
    <style>

        .node circle {
            fill: #fff;
            stroke: steelblue;
            stroke-width: 3px;
        }

        .node text { font: 12px sans-serif; }

        .node--internal text {
            text-shadow: 0 1px 0 #fff, 0 -1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff;
        }

        .link {
            fill: none;
            stroke: #ccc;
            stroke-width: 2px;
        }

        #debug_alert {
            font-family: Helvetica;
            font-size: 30px;
        }

        .region_label {
            font-family: Helvetica;
            font-size: 50px;
        }

    </style>
</head>

<body>
<div id="main-content" class="container">

    <div class="row">
        <div class="col-md-12">
            <div id="debug_alert" class="alert alert-success text-center" role="alert">
                JVM G1 垃圾回收算法 动画演示
            </div>
        </div>
    </div>
    <div class="row">
        <div class="col-md-12">
            <div class="float-md-right">
                <button id="run" type="button" class="btn btn-primary">运行</button>
                <button id="debug" type="button" class="btn btn-warning">调试</button>
                <button id="step" type="button" class="btn btn-success">下一步</button>
                <button id="pause" type="button" class="btn btn-secondary">暂停</button>
                <button id="stop" type="button" class="btn btn-secondary">停止</button>

                <button id="go" type="button" class="btn btn-info">继续</button>
                <button id="mark" type="button" class="btn btn-secondary">标记</button>
                <button id="copy" type="button" class="btn btn-secondary">移动</button>
                <button id="swap" type="button" class="btn btn-secondary">清除</button>
            </div>
        </div>
    </div>
    <div class="row">
        <div class="col-md-12">

            <svg id="jvm" width="1200" height="1500">
                <defs>
                    <marker id="markerArrow" markerWidth="10" markerHeight="7"
                            refX="0" refY="3.5"  orient="auto">
                        <polygon points="0 0, 10 3.5, 0 7" />
                    </marker>
                </defs>

            </svg>

        </div>
    </div>

</div>

    <script>
        let object_length = 32;
        let region_length = object_length * 5;
        let heap_length = region_length * 5;
        let colorCategory10 = d3.scaleOrdinal(d3.schemeCategory10);
        let colorPastel2 = d3.scaleOrdinal(d3.schemePastel2);
        let margin = {top: 40, right: 90, bottom: 50, left: 150},
            width = 1200 - margin.left - margin.right,
            height = 1000 - margin.top - margin.bottom;

        let svg = d3.select("#jvm")
                .attr("width", width + margin.left + margin.right)
                .attr("height", height + margin.top + margin.bottom),
            g = svg.append("g")
                .attr("transform",
                    "translate(" + margin.left + "," + margin.top + ")");

        init_heap();
        let eden=[];
        let markobj=[];
        let id=0;
        for(let j=0;j<9;j=j+2){
            for(let i=0;i<25;i++){
                let g1obj={id:id,address:i,age:0,region:{id:j}};
                allocate(g1obj);
                eden.push(g1obj);
                id++;
            }
        }

        for(let j=0;j<5;j++){
            for(let i=0;i<3;i++){
                let index =Math.floor(Math.random() * (eden.length));
                let obj = eden[index];
                mark(obj);
                markobj.push(obj);
            }
        }
        sleep(2000).then(()=>{
            for(let i=0;i<markobj.length;i++){
                let mark =markobj[i];
                let obj=new Object();
                obj.id=mark.id;
                obj.reginId=mark.region.id;
                obj.address=mark.address;
                obj.age=mark.age+1;
                obj.fromReginId=mark.region.id;
                obj.toReginId=11;
                copy(obj);
            }
        });


        function sweep(data) {
            d3.selectAll('.region-obj-' + data.region.id)
                .transition('sweep')
                .duration(1000)
                .delay(1000)
                .remove();
        }

        function mark(obj) {
            let obj_g = d3.select('#obj-' + obj.id);
            obj_g.select('circle')
                .transition()
                .duration(1000)
                .attr("transform", "scale(1.2)")
                .attr("fill", "white");

        }

        function copy(data) {
            let obj_g = d3.select('#obj-' + data.id);
            let region = d3.select('#region-' + data.toReginId);
            let {x, y} = position(region, data.address);
            obj_g.classed('region-obj-' + data.fromReginId, false)
                .classed('region-obj-' + data.toReginId, true)
                .transition()
                .duration(1000)
                .attr("transform",
                    "translate(" + (x + object_length / 2) + "," + (y + object_length / 2) + ")");
            obj_g.select('circle')
                .transition()
                .duration(1000)
                .attr("transform", "scale(0.95)")
                .attr("fill", colorCategory10(Math.floor(Math.random() * 10)));
            obj_g.select("text")
                .attr("x", -5)
                .attr("y", 5)
                .text(data.age)
                .attr("stroke", "black");
        }


        function allocate(obj) {
            let region = d3.select('#region-' + obj.region.id);
            let {x, y} = position(region, obj.address);
            let obj_g = g.append('g')
                .attr("transform",
                    "translate(" + (x + object_length / 2) + "," + (y + object_length / 2) + ")")
                .attr("id", 'obj-' + obj.id)
                .classed('region-obj-' + obj.region.id, true);

            obj_g.append("circle")
                .attr("cx", 0)
                .attr("cy", 0)
                .attr("r", object_length / 2)
                .attr("fill", colorCategory10(Math.floor(Math.random() * 10)))
                .attr("stroke", "blue");
            obj_g.append("text")
                .attr("x", -5)
                .attr("y", 5)
                .text(obj.age)
                .attr("stroke", "black");
        }
        function position(region, index) {
            return {
                x: region.data()[0].x + index % 5 * object_length,
                y: region.data()[0].y + Math.floor(index / 5) * object_length
            }
        }

        function init_heap() {
        // set the dimensions and margins of the diagram
            let treeData = {
                key: 625,
                index: 0,
                children: []
            };

            for (let i = 0; i < 25; i++) {
                let region = {
                    key: i,
                    index: i,
                    children: []
                }
                treeData.children.push(region);
            }

            let hierarchy = d3.hierarchy(treeData);
            let root = treemap(hierarchy);
            console.log(root)
            g.selectAll(".region")
                .data(root.decendents)
                .enter()
                .append("rect")
                .classed('region', true)
                .attr("id", d => {
                    return 'region-' + d.id
                })
                .attr("x", d => {
                    return d.x
                })
                .attr("y", d => {
                    return d.y;
                })
                .attr("width", d => {
                    return d.length;
                })
                .attr("height", d => {
                    return d.length;
                })
                .attr("fill", d => {
                    let i = d.id;
                    let color = colorPastel2(0);
                    if (i == 11 || i == 13) {
                        color = colorPastel2(2);
                    } else if (i == 24) {
                        color = colorPastel2(3);
                    } else if (i % 2 == 0 && i < 10) {
                        color = colorPastel2(0);
                    } else {
                        color = colorPastel2(1);
                    }
                    return color;
                })
                .attr("stroke", "blue");

            g.selectAll(".region_label")
                .data(root.decendents)
                .enter()
                .append("text")
                .attr("x", d => {
                    return d.x + d.length / 2;
                })
                .attr("y", d => {
                    return d.y + d.length / 2;
                })
                .classed('region_label', true)
                .style("text-anchor", "middle")
                .text(function (d) {
                    let i = d.id;
                    let text = 'E';
                    if (i == 11 || i == 13) {
                        text = 'S';
                    } else if (i == 24) {
                        text = 'H';
                    } else if (i % 2 == 0 && i < 10) {
                        text = 'E';
                    } else {
                        text = 'O';
                    }
                    return text;
                });
        }

        function treemap(root) {
            if (root.depth == 0) { // heap
                root.x = 0;
                root.y = 0;
                root.length = heap_length;
            }
            if (root.depth == 1) { // region
                let index = root.data.index;
                root.x = index % 5 * region_length;
                root.y = Math.floor(index / 5) * region_length;
                root.length = region_length;
                root.id = index;
            }
            if (root.depth == 2) { // object
                let index = root.data.index;
                root.x = root.parent.x + index % 5 * object_length;
                root.y = root.parent.y + Math.floor(index / 5) * object_length;
                root.length = object_length;
            }
            if (root.depth > 2) { // object
                console.log("should not be here");
                return root;
            }
            if (root.children == null) {
                return root;
            }
            root.decendents = root.children;
            for (let i = 0; i < root.children.length; i++) {
                let child = treemap(root.children[i]);
                root.decendents.concat(child.decendents);
            }
            return root;

        }
        function sleep(wait) {
            return new Promise((res, rej) => {
                setTimeout(() => {
                    this.num = 1;
                    res(wait);
                }, wait);
            });
        }
        let s = Snap('#jvm');
        let offset = 50;
        let gback = s.g().attr({stroke: 'gray'});
        for (let i = 0; i <= 1600 / offset; i++) {
            gback.line(i * offset, 0, i * offset, 1600)
                .attr({"stroke-width": '1', "stroke-dasharray": "5, 5"});
            gback.line(0, i * offset, 1600, i * offset)
                .attr({"stroke-width": '1', "stroke-dasharray": "5, 5"});
            gback.text(0, i * offset, i * offset);
            gback.text(i * offset, 20, i * offset);
        }
    </script>
</body>

</html>